package cn.bugstack.aop;

import cn.bugstack.trigger.api.dto.ActivityDrawResponseDTO;
import cn.bugstack.trigger.api.response.Response;
import cn.bugstack.types.annotations.DCCValue;
import cn.bugstack.types.annotations.RateLimiterAccessInterceptor;
import cn.bugstack.types.enums.ResponseCode;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 限流切面
 * <p>
 * 基于Spring AOP和Guava RateLimiter实现的接口限流组件。
 * 该组件通过注解方式标记需要限流的接口方法，并使用令牌桶算法控制接口的访问频率。
 * 同时支持黑名单机制，对频繁触发限流的用户进行更长时间的限制。
 * </p>
 * <p>
 * 使用方式：在需要限流的方法上添加 @RateLimiterAccessInterceptor 注解
 * 例如：@RateLimiterAccessInterceptor(key = "userId", fallbackMethod = "回调方法名", permitsPerSecond = 1.0, blacklistCount = 1)
 * </p>
 *
 * @author tang
 */
@Slf4j
@Aspect
@Component
public class RateLimiterAOP {

    /**
     * 限流功能开关
     * <p>
     * open: 开启限流功能
     * close: 关闭限流功能（默认）
     * </p>
     */
    @DCCValue("rateLimiterSwitch:close")
    private String rateLimiterSwitch;

    /**
     * 个人限频记录缓存
     * <p>
     * 使用Guava Cache存储用户的RateLimiter对象，有效期1分钟
     * key: 用户标识（如用户ID）
     * value: 对应用户的限流器对象
     * </p>
     */
    private final Cache<String, RateLimiter> loginRecord = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build();

    /**
     * 个人限频黑名单缓存
     * <p>
     * 存储触发黑名单的用户，有效期24小时
     * 在分布式环境下，可以将黑名单信息存储到Redis中实现集群共享
     * key: 用户标识（如用户ID）
     * value: 用户触发限流的次数
     * </p>
     */
    private final Cache<String, Long> blacklist = CacheBuilder.newBuilder()
            .expireAfterWrite(24, TimeUnit.HOURS)
            .build();

    /**
     * 定义切入点
     * <p>
     * 匹配所有使用 @RateLimiterAccessInterceptor 注解标记的方法
     * </p>
     */
    @Pointcut("@annotation(cn.bugstack.types.annotations.RateLimiterAccessInterceptor)")
    public void aopPoint() {
    }

    /**
     * 环绕通知 - 实现限流逻辑
     * <p>
     * 1. 检查限流开关是否开启
     * 2. 获取用于标识限流对象的key值
     * 3. 检查用户是否在黑名单中
     * 4. 获取或创建RateLimiter对象，尝试获取令牌
     * 5. 如果获取成功，则执行原方法；否则更新黑名单计数并执行降级方法
     * </p>
     *
     * @param jp 连接点，包含被拦截方法的信息和执行环境
     * @param rateLimiterAccessInterceptor 注解实例，包含限流配置参数
     * @return 原方法的执行结果或降级方法的结果
     * @throws Throwable 可能抛出的异常
     */
    @Around("aopPoint() && @annotation(rateLimiterAccessInterceptor)")
    public Object doRouter(ProceedingJoinPoint jp, RateLimiterAccessInterceptor rateLimiterAccessInterceptor) throws Throwable {
        // 0. 限流开关【open 开启、close 关闭】关闭后，不会走限流策略
        if (StringUtils.isBlank(rateLimiterSwitch) || "close".equals(rateLimiterSwitch)) {
            return jp.proceed();
        }

        String key = rateLimiterAccessInterceptor.key();
        if (StringUtils.isBlank(key)) {
            throw new RuntimeException("annotation RateLimiter uId is null！");
        }
        // 获取拦截字段
        String keyAttr = getAttrValue(key, jp.getArgs());
        log.info("aop attr {}", keyAttr);

        // 黑名单拦截
        if (!"all".equals(keyAttr) && rateLimiterAccessInterceptor.blacklistCount() != 0 
                && null != blacklist.getIfPresent(keyAttr) 
                && blacklist.getIfPresent(keyAttr) > rateLimiterAccessInterceptor.blacklistCount()) {
            log.info("限流-黑名单拦截(24h)：{}", keyAttr);
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }

        // 获取限流 -> Guava 缓存1分钟
        RateLimiter rateLimiter = loginRecord.getIfPresent(keyAttr);
        if (null == rateLimiter) {
            rateLimiter = RateLimiter.create(rateLimiterAccessInterceptor.permitsPerSecond());
            loginRecord.put(keyAttr, rateLimiter);
        }

        // 限流拦截
        if (!rateLimiter.tryAcquire()) {
            if (rateLimiterAccessInterceptor.blacklistCount() != 0) {
                if (null == blacklist.getIfPresent(keyAttr)) {
                    blacklist.put(keyAttr, 1L);
                } else {
                    blacklist.put(keyAttr, blacklist.getIfPresent(keyAttr) + 1L);
                }
            }
            log.info("限流-超频次拦截：{}", keyAttr);
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }

        // 返回结果
        return jp.proceed();
    }

    /**
     * 调用用户配置的回调方法，当拦截后，返回回调结果
     * <p>
     * 通过反射机制调用目标类中的降级方法，处理被限流的请求
     * </p>
     *
     * @param jp 连接点对象
     * @param fallbackMethod 回调方法名
     * @return 回调方法的执行结果
     * @throws NoSuchMethodException 方法不存在时抛出
     * @throws InvocationTargetException 调用目标异常
     * @throws IllegalAccessException 非法访问异常
     */
    private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        return method.invoke(jp.getThis(), jp.getArgs());
    }

    /**
     * 获取属性值
     * <p>
     * 实际根据自身业务调整，主要是为了获取通过某个值做拦截
     * 支持直接传入字符串参数或复杂对象的属性获取
     * </p>
     *
     * @param attr 属性名
     * @param args 方法参数数组
     * @return 提取到的属性值
     */
    public String getAttrValue(String attr, Object[] args) {
        if (args[0] instanceof String) {
            return args[0].toString();
        }
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                // filedValue = BeanUtils.getProperty(arg, attr);
                // fix: 使用lombok时，uId这种字段的get方法与idea生成的get方法不同，会导致获取不到属性值，改成反射获取解决
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("获取路由属性值失败 attr：{}", attr, e);
            }
        }
        return filedValue;
    }

    /**
     * 获取对象的特定属性值
     * <p>
     * 通过反射机制获取对象的属性值
     * </p>
     *
     * @param item 目标对象
     * @param name 属性名
     * @return 属性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            Field field = getFieldByName(item, name);
            if (field == null) {
                return null;
            }
            field.setAccessible(true);
            Object o = field.get(item);
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    /**
     * 根据名称获取字段
     * <p>
     * 该方法同时兼顾继承类获取父类的属性
     * 先尝试获取当前类的字段，如果不存在，则尝试获取父类的字段
     * </p>
     *
     * @param item 目标对象
     * @param name 字段名
     * @return 查找到的字段对象
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

}
